Skip to content

fix(server): fetch change request heads from each host's ref namespace - #6532

Open
lnieuwenhuis wants to merge 6 commits into
pingdotgg:mainfrom
lnieuwenhuis:dev/fix/gitlab-merge-request-refs
Open

fix(server): fetch change request heads from each host's ref namespace#6532
lnieuwenhuis wants to merge 6 commits into
pingdotgg:mainfrom
lnieuwenhuis:dev/fix/gitlab-merge-request-refs

Conversation

@lnieuwenhuis

@lnieuwenhuis lnieuwenhuis commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What Changed

fetchPullRequestBranch and fetchPullRequestHeadCommit in GitVcsDriverCore no longer build
GitHub's refs/pull/<n>/head themselves. They now take the ref to fetch, and
GitManager.preparePullRequestThread names it from the host that resolved the change request:

  • github -> refs/pull/<n>/head (unchanged)
  • gitlab -> refs/merge-requests/<n>/head
  • bitbucket / azure-devops -> no such ref exists, so the head branch is fetched from the
    primary remote by name instead. Skipped for a cross-repository head, where a branch of the same
    name on the primary remote is somebody else's work; that case now reports why it cannot resolve
    the head rather than failing on a fetch that could never have worked.

The mapping is one pure function, changeRequestHeadRef, next to the other pure source-control
helpers in apps/server/src/sourceControl/SourceControlProvider.ts. The host kind comes from
ChangeRequest.provider on the change request that preparePullRequestThread has already
resolved, so nothing extra is looked up and the ref can never disagree with the number it was
built from.

Why

Worktree checkout of a GitLab merge request fails for every MR (#6448). GitLab publishes the head
at refs/merge-requests/<iid>/head, never under refs/pull, so the fetch exits 128 with
fatal: couldn't find remote ref refs/pull/533/head. Both the primary and the fallback attempt in
materializePullRequestHeadBranch ran the same hardcoded refspec, so both failed identically and
preparePullRequestThread failed for every MR in worktree mode. Local mode was unaffected because
it goes through the provider's own checkoutChangeRequest. Azure DevOps and Bitbucket were broken
the same way, since neither publishes refs/pull/<n>/head either.

The head ref is host knowledge, and the git driver has none: it only knows a repository path and a
remote name. The provider registry already resolves the host for the cwd, and the resolved change
request already carries its kind, so the smallest honest fix is to let the caller name the ref and
keep the driver purely git-level.

Alternatives considered and rejected:

  • A changeRequestHeadRef method on the SourceControlProvider service. Four provider
    implementations plus two registry wrappers (unsupportedProvider, bindProviderContext) would
    each need an entry, and every one would return a constant derived from the kind. The caller
    already has the kind; the indirection buys nothing.
  • Detecting the host from the remote URL inside the git driver. Self-hosted GitLab is not
    reliably identifiable from its URL, which is exactly why the registry has a discovery probe to
    refine an unknown remote. It would also duplicate provider detection in the git layer.
  • Always fetching headBranch by name (the issue's third suggestion). It fixes same-repository
    MRs but gives up what the published ref is for: closed merge requests, heads whose branch was
    deleted or renamed, and fork heads. It is used here only for hosts that publish no ref at all.

Verification

No real self-hosted GitLab remote was exercised — there is none available here. The new tests build
real local git repositories and publish the head under refs/merge-requests/533/head and nowhere
else, which is the shape that made the original fetch fail.

  • cd apps/server && pnpm exec vp test run src/git/GitManager.test.ts src/sourceControl/SourceControlProvider.test.ts
  • cd apps/server && pnpm exec tsgo --noEmit
  • pnpm exec vp fmt --check and pnpm exec vp lint on the changed files

Both new GitManager tests were confirmed to fail against the old ref layout (exit 128 on
GitVcsDriver.fetchPullRequestBranch, both attempts) before the fix, and the existing GitHub tests
that publish to refs/pull/<n>/head still pass.

Closes #6448

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes — no UI change
  • I included a video for animation/interaction changes — not applicable

Note

Medium Risk
Touches PR/MR worktree checkout and git fetch paths across providers; behavior changes for hosts without a published head ref and for cross-repo materialization errors.

Overview
Fixes GitLab (and similar) worktree preparation by stopping hardcoded GitHub refs/pull/<n>/head fetches. The git layer now takes an explicit head ref from a new changeRequestHeadRef helper (GitHub vs GitLab namespaces; null for Bitbucket/Azure DevOps).

GitManager.preparePullRequestThread resolves that ref from the change request’s provider, passes it through head materialization and reused-worktree refresh, and when there is no published head ref fetches the head branch on the primary remote instead—while cross-repository heads without resolvable fork metadata get a clear GitManagerError instead of a doomed refs/pull fallback.

Azure DevOps changes decode fork provenance (isCrossRepository, headRepositoryNameWithOwner) and qualify az repos show with project for fork clone URLs. Tests cover GitLab MR refs, Bitbucket branch-only heads, and Azure fork parsing.

Reviewed by Cursor Bugbot for commit e91fece. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fetch change request heads from each host's ref namespace in GitManager

  • Adds changeRequestHeadRef to SourceControlProvider to return the GitHub pull-request or GitLab merge-request head ref, or null for Bitbucket and Azure DevOps
  • Reworks materializePullRequestHeadBranch in GitManager.ts to fetch the provider-selected head ref instead of always deriving a GitHub ref from the pull-request number; for hosts without a head ref, same-repository requests fall back to the primary remote branch and cross-repository requests fail with a specific GitManagerError
  • Updates fetchPullRequestBranch and fetchPullRequestHeadCommit in GitVcsDriverCore.ts to accept an explicit head-ref input rather than a pull-request number
  • Extends Azure DevOps pull-request parsing in azureDevOpsPullRequests.ts to preserve fork provenance, cross-repository flags, and project-qualified head repository identity
  • Behavioral Change: Bitbucket and Azure DevOps no longer attempt a GitHub-style refs/pull/<n>/head fetch; reused worktrees on those hosts refresh from the checked-out branch's upstream only when that upstream names the requested head branch

Macroscope summarized e91fece.

Worktree checkout of a GitLab merge request failed for every MR, because
fetchPullRequestBranch and fetchPullRequestHeadCommit built GitHub's
refs/pull/<n>/head themselves. GitLab publishes the head at
refs/merge-requests/<iid>/head, so the fetch exited 128 and both the
primary and the fallback attempt failed identically.

The driver now takes the ref to fetch and GitManager names it from the
host that resolved the change request. Hosts that publish no such ref at
all (Bitbucket, and Azure DevOps, which publishes only the merge result)
fetch the head branch from the primary remote instead, except for a
cross-repository head, where a branch of the same name there is somebody
else's work.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 95795b10-7959-418f-b114-a09b2247bff5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 13, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The PR changes existing change-request worktree behavior across several source-control hosts and adds line-level suppressions for a static-analysis diagnostic in new tests. Those suppressions require human review under the applicable approval criteria.

You can add or adjust custom eligibility rules. Learn more.

@lnieuwenhuis

Copy link
Copy Markdown
Contributor Author

Closing this one — open since Aug 13 with no human review, and the git manager / VCS driver code it touches has moved on main since. Happy to reopen against current main if the per-host ref namespace fix is still wanted.

@lnieuwenhuis

Copy link
Copy Markdown
Contributor Author

Reopening — I re-checked against current main and the staleness worry that made me close this was wrong.

  • The bug is still there: GitVcsDriverCore.ts still hardcodes refs/pull/<n>/head at both fetch sites, and there is no refs/merge-requests/<iid>/head handling anywhere in apps/server/src. GitLab merge request heads still can't be fetched.
  • The no-head-ref fallback that landed in feat: multi-provider pull requests page with in-app reviews #4849 is already accounted for by this branch — it predates the branch point, so this isn't a duplicate of it.
  • The branch still merges cleanly into current main, and every fetchPullRequestBranch/fetchPullRequestHeadCommit call site in the merged tree uses the new headRef field — no caller was added on main that the signature change would miss.

@t3dotgg t3dotgg added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. and removed vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Aug 24, 2026
@lnieuwenhuis

Copy link
Copy Markdown
Contributor Author

@t3dotgg @juliusmarminge I'd still like your thoughts on this, you can close this PR if it's not a worry for you, but every bug squashed is one less banana peel for the future, right?

Comment thread apps/server/src/git/GitManager.ts Outdated
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 27, 2026
…request-refs

# Conflicts:
#	apps/server/src/git/GitManager.ts
@macroscopeapp
macroscopeapp Bot dismissed their stale review September 2, 2026 09:10

Dismissing prior approval to re-evaluate f1befa3

Comment thread apps/server/src/git/GitManager.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 679d16f. Configure here.

Comment thread apps/server/src/sourceControl/azureDevOpsPullRequests.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Worktree checkout of GitLab merge requests always fails — fetchPullRequestBranch hardcodes GitHub's refs/pull/<n>/head

2 participants